//
//  GCD.swift
//  RaveLand
//
//  Created by zhuxuhong on 2021/4/26.
//  Copyright © 2021 CSLY. All rights reserved.
//

import Foundation
import UIKit

public typealias GCDTimer = DispatchSourceTimer

public enum GCD {
    public static func asyncInGlobalQueue(_ handler: @escaping BlockVoid){
        Queue.global().queue.async(execute: handler)
    }
    public static func asyncInMainQueue(_ handler: @escaping BlockVoid) {
        Queue.main.queue.async(execute: handler)
    }
    
    public static func dispatchTimer(
        delay: TimeInterval = 0,
        interval: TimeInterval = 0,
        repeats: Bool = true,
        autoResume: Bool = true,
        in queue: DispatchQueue = .main,
        handler: @escaping BlockVoid
    ) -> GCDTimer {
        let deadline = repeats ? delay : interval
        let repeating = repeats ? interval : 0
        let timer = DispatchSource.makeTimerSource(flags: [], queue: queue)
        timer.schedule(deadline: .now() + deadline, repeating: repeating, leeway: .seconds(0))
        timer.setEventHandler { [weak timer] in
            handler()
            if !repeats {
                timer?.cancel()
            }
        }
        if autoResume {
            timer.resume()
        }
        return timer
    }
}

extension GCD {
    public enum Group {
    }
    public enum Queue {
        case main
        case global(qos: DispatchQoS.QoSClass = .default)
        case custom(
            label: String,
            qos: DispatchQoS = .unspecified,
            attributes: DispatchQueue.Attributes = [],
            autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit,
            target: DispatchQueue? = nil
        )
        
        public static func operation(maxConcurrentCount count: Int = -1) -> OperationQueue {
            let queue = OperationQueue()
            queue.maxConcurrentOperationCount = count
            return queue
        }
        
        public var queue: DispatchQueue {
            switch self {
            case .main:
                return .main
            case .global(let qos):
                return .global(qos: qos)
            case .custom(let label, let qos, let attributes, let autoreleaseFrequency, let target):
                return .init(label: label, qos: qos, attributes: attributes, autoreleaseFrequency: autoreleaseFrequency, target: target)
            }
        }
    }
}

extension GCD.Group {
    public struct CompletionError {
        var taskId: Item.TaskId
        var error: Error
        public init(taskId: Item.TaskId, error: Error){
            self.taskId = taskId
            self.error = error
        }
    }
    public typealias Completion = BlockT<CompletionError?>
    
    public class Item {
        public typealias TaskId = String
        
        var taskId: TaskId = ""
        public var handler: BlockVoid?
        public internal(set) var completion: Completion?
        
        public init(taskId: TaskId){
            self.taskId = taskId
        }
    }
    
    public static func async(
        items: [Item],
        notify queue: DispatchQueue = .main,
        timeout seconds: TimeInterval? = nil,
        completion: @escaping BlockT<[CompletionError]>
    ) {
        let groupQueue = GCD.Queue.custom(label: "GCD.Group.queue", attributes: .concurrent).queue
        let group = DispatchGroup()
        
        var errors: [CompletionError] = []
        for item in items
        {
            item.completion = { error in
                if let error = error {
                    errors.append(error)
                }
                group.leave()
            }
            
            groupQueue.async(group: group) {
                group.enter()
                item.handler?()
            }
        }
        
        group.notify(queue: queue, execute: {
            completion(errors)
        })
    }
}

extension GCD.Queue {
    public func asyncAfter(
        seconds: TimeInterval,
        handler: @escaping BlockVoid
    ) {
        queue.asyncAfter(deadline: .now() + seconds, execute: handler)
    }
}

extension GCD {
    public static func asyncAfter(
        seconds: TimeInterval,
        in queue: DispatchQueue = Queue.global().queue,
        handler: @escaping BlockVoid
    ) {
        queue.asyncAfter(deadline: .now() + seconds, execute: handler)
    }
}

extension GCD {
    public enum Task {
        public static func cancel(_ operations: [Operation]) {
            operations.forEach{ op in
                if !op.isCancelled, !op.isFinished {
                    op.cancel()
                }
            }
        }
        
        private static func executeResult(of operations: [Operation]) -> ExecuteResult {
            var result = ExecuteResult()
            for op in operations {
                if op.isCancelled {
                    result.cancelled.append(op.taskId)
                }
                else if op.isTimedout {
                    result.timedout.append(op.taskId)
                }
                else {
                    result.success.append(op.taskId)
                }
            }
            result.isAllSuccess = result.success.count == operations.count
            return result
        }
        
        public static func async(
            operations: [Operation],
            queuePolicy: QueuePolicy = .concurrent(maxCount: -1),
            timeoutPolicy: TimeoutPolicy = .waiting,
            notify notifyQueue: DispatchQueue = .main,
            completion: @escaping BlockT<ExecuteResult>
        )
        {
            GCD.asyncInGlobalQueue
            {
                var count = -1
                switch queuePolicy {
                case .serial:
                    count = 1
                case .concurrent(let maxCount):
                    count = maxCount
                }
                
                var isFinished = false
                let opQueue = GCD.Queue.operation(maxConcurrentCount: count)
                let newOperations = operations.filter{
                    !$0.isFinished && !opQueue.operations.contains($0)
                }
                for op in newOperations {
                    op.timedoutHandler = {
                        switch timeoutPolicy {
                        case .cancelOthers:
                            guard !isFinished else { break }
                            cancel(newOperations)
                        default: break
                        }
                        return timeoutPolicy
                    }
                    opQueue.addOperation(op)
                }
                opQueue.waitUntilAllOperationsAreFinished()
                
                notifyQueue.async{
                    isFinished = true
                    completion(self.executeResult(of: operations))
                }
            }
        }
    }
    
}

extension GCD.Task {
    public enum QueuePolicy {
        case serial
        case concurrent(maxCount: Int)
    }
    public enum TimeoutPolicy {
        case waiting
        case continueNext
        case cancelOthers
    }
    public struct ExecuteResult {
        public fileprivate(set) var success: [String] = []
        public fileprivate(set) var cancelled: [String] = []
        public fileprivate(set) var timedout: [String] = []
        public fileprivate(set) var isAllSuccess = false
    }
}

extension GCD.Task {
    public class Operation: Foundation.Operation {
        public var taskId: String
        public var timeouts: TimeInterval
        public var handler: BlockVoid?
        public private(set) var isTimedout = false
        internal var timedoutHandler: BlockR<TimeoutPolicy>?
        
        fileprivate lazy var completion: BlockVoid = {
            return { [weak self] in
                guard let strongSelf = self else { return }
                strongSelf.semaphore.signal()
            }
        }()
        public override var completionBlock: (() -> Void)? {
            set{}
            get {
                return completion
            }
        }
        private lazy var semaphore = DispatchSemaphore(value: 0)
        
        public enum Status {
            case ready, timedout, finished, cancelled
        }
        public var status: Status {
            switch (isTimedout, isFinished, isCancelled) {
            case (true, _, _): return .timedout
            case (_, true, _): return .finished
            case (_, _, true): return .cancelled
            default: return .ready
            }
        }
        
        public override var description: String {
            return "[Task: \(taskId)] [Status: \(status)]"
        }
        
        public init(
            taskId: String,
            timeout seconds: TimeInterval = 0
        ) {
            self.taskId = taskId
            self.timeouts = seconds
        }
        
        public override func main()
        {
            guard !isCancelled, let handler = handler else {
                return
            }
            GCD.Queue.global(qos: .userInitiated).queue.async(execute: handler)
            
            if timeouts > 0
            {
                switch (semaphore.wait(timeout: .now() + timeouts)) {
                case .timedOut:
                    isTimedout = true
                    guard let policy = timedoutHandler?() else {
                        return
                    }
                    switch policy {
                    case .continueNext: semaphore.signal()
                    default: break
                    }
                case .success:
                    break
                }
            }
            else {
                semaphore.wait()
            }
        }
    }
}
